package com.zx.tfw.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.zx.tfw.dao.TechnicalFrameworkMapper;
import com.zx.tfw.dao.TechnicalManufacturerMapper;
import com.zx.tfw.entity.TechnicalFramework;
import com.zx.tfw.entity.TechnicalManufacturer;
import com.zx.tfw.enums.CountryEnum;
import com.zx.tfw.enums.LanguageEnum;
import com.zx.tfw.enums.LicenseEnum;
import com.zx.tfw.enums.OSEnum;
import com.zx.tfw.enums.SkilledEnum;
import com.zx.tfw.enums.TagEnum;
import com.zx.tfw.enums.TypeEnum;
import com.zx.tfw.model.TechnicalFrameworkDto;
import com.zx.tfw.model.TfwStatisticsModel;
import com.zx.tfw.model.TfyTypeModel;
import com.zx.tfw.model.tfwByTypeCodeDto;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Slf4j
@Service
public class TechnicalFrameworkService {

    @Autowired
    private TechnicalFrameworkMapper technicalFrameworkDao;

    @Autowired
    private TechnicalManufacturerMapper technicalManufacturerDao;

    public List<TechnicalFramework> findAll() {
        return this.technicalFrameworkDao.selectList(Wrappers.lambdaQuery());
    }

    public void save(TechnicalFramework tfw) {
        log.info("TechnicalFrameworkService#save tfw={}", tfw);
        this.technicalFrameworkDao.insert(tfw);
    }

    public void update(TechnicalFramework tfw) {
        log.info("TechnicalFrameworkService#update tfw={}", tfw);
        this.technicalFrameworkDao.updateById(tfw);
    }

    public TechnicalFramework findById(Integer id) {
        log.info("TechnicalFrameworkService#findById id={}", id);
        return this.technicalFrameworkDao.selectById(id);
    }

    public TechnicalFrameworkDto findDtoById(Integer id) {
        log.info("TechnicalFrameworkService#findDtoById id={}", id);
        TechnicalFramework data = this.findById(id);
        TechnicalFrameworkDto dto = new TechnicalFrameworkDto();
        if (data != null) {
            BeanUtils.copyProperties(data, dto);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

            // 格式化时间
            dto.setBeginTimeStr(data.getBeginTime() == null ? "" : sdf.format(data.getBeginTime()));
            dto.setLastUpdateTimeStr(data.getLastUpdateTime() == null ? "" : sdf.format(data.getLastUpdateTime()));

            dto.setOsCodeStr(OSEnum.asMap().get(data.getOsCode()).getName());
            dto.setLicenseCodeStr(LicenseEnum.asMap().get(data.getLicenseCode()).getName());
            dto.setLanguageCodeStr(LanguageEnum.asMap().get(data.getLanguageCode()).getName());
            dto.setTagsCodeStr(TagEnum.asMap().get(data.getTagsCode()).getName());

            TechnicalManufacturer selectById = this.technicalManufacturerDao.selectById(dto.getManufacturerCode());
            //TechnicalManufacturer orElse = this.technicalManufacturerDao.findById(dto.getManufacturerCode()).orElse(new TechnicalManufacturer());
            dto.setManufacturerCodeStr(selectById.getMName());

            dto.setTypeCodeStr(TypeEnum.asMap().get(data.getTypeCode()).getName());
            dto.setSkilledCodeStr(SkilledEnum.asMap().get(data.getSkilledCode()).getName());
            dto.setSkilledColor(SkilledEnum.asMap().get(data.getSkilledCode()).getBackgroundColor());
            dto.setCountryCodeStr(CountryEnum.asMap().get(data.getCountryCode()).getName());

            // 切割网址
            dto.setSourcesUrlArr(
                    StringUtils.isEmpty(data.getSourcesUrl()) ? new String[0] : data.getSourcesUrl().split(","));
            dto.setUccnUrlArr(StringUtils.isEmpty(data.getUccnUrl()) ? new String[0] : data.getUccnUrl().split(","));
            dto.setForumUrlArr(StringUtils.isEmpty(data.getForumUrl()) ? new String[0] : data.getForumUrl().split(","));

        }

        return dto;
    }

    /**
     * @return
     * @title 分组统计数据--按照技术类型分组{返回的数据是个table， 单行最多10个元素(加上头行有11个元素)}
     * @author: xue.zhang
     * @date 2019年1月31日下午2:29:13
     */
    public List<TfyTypeModel> classificationByType() {
        log.info("TechnicalFrameworkService#classificationByType");
        int lineNum = 10;
        List<TechnicalFramework> all = this.technicalFrameworkDao
                .selectList(Wrappers.lambdaQuery());
        Map<Integer, List<TechnicalFramework>> collect = all.stream()
                .collect(Collectors.groupingBy(TechnicalFramework::getTypeCode));
        List<TfyTypeModel> result = new ArrayList<>();
        for (TypeEnum typeEnum : TypeEnum.values()) {
            int typeCode = typeEnum.getCode();
            if (collect.containsKey(typeCode)) {
                List<TechnicalFramework> list = collect.get(typeCode);
                Lists.partition(list, lineNum).stream().forEach(x -> {
                    List<tfwByTypeCodeDto> typeData = x.stream().map(ele -> new tfwByTypeCodeDto(ele))
                            .collect(Collectors.toList());
                    result.add(new TfyTypeModel(typeEnum, typeData));
                });
            } else {
                result.add(new TfyTypeModel(typeEnum, new ArrayList<>()));
            }
        }
        // 填充字段。保证每行有lineNum个元素
        for (TfyTypeModel ts : result) {
            log.debug("填充信息={}", ts);
            int size = ts.getTfws().size();
            int fullSize = lineNum - size;//需要填充几个
            for (int i = 0; i < fullSize; i++) {
                ts.getTfws().add(new tfwByTypeCodeDto());
            }
        }
        return result;
    }

    //############################### 以下方法为分类排序器 ##############################################//

    /**
     * @param classifier 分组器【必传】。为数据分组提供依据。	类型为  R
     * @param keyConverter 排序器【必传】，字段真实的类型 K
     * @param templateData 模板数据【必传】，作为返回数据的基准，将查询到的结果保存在fullData中
     * @param <K>
     * @return
     * @title 分类排序器(高级方法)
     * @author: xue.zhang
     * @date 2019年2月2日上午9:45:33
     */
    public <R, K> List<TfwStatisticsModel<K, TechnicalFramework>> handler(
            Function<TechnicalFramework, R> classifier,
            Function<R, K> keyConverter,
            List<TfwStatisticsModel<K, TechnicalFramework>> templateData
    ) {
        if (CollectionUtils.isEmpty(templateData)) {
            return null;
        }
        // 查询数据
        List<TfwStatisticsModel<K, TechnicalFramework>> queryData = this.handler(classifier, null, keyConverter);
        Map<K, List<TechnicalFramework>> tempData = queryData.stream()
                .collect(Collectors.toMap(TfwStatisticsModel::getKey, TfwStatisticsModel::getData));

        for (TfwStatisticsModel<K, TechnicalFramework> data : templateData) {
            K key = data.getKey();
            if (tempData.containsKey(key)) {
                data.setData(tempData.get(key));
                tempData.remove(key);//找到就移除数据
            }
        }
        if (!tempData.isEmpty()) {
            log.warn("有模板数据中不存在的类型数据{}", tempData.keySet());
        }

        return templateData;
    }

    /**
     * @param classifier 分组器【必传】。为数据分组提供依据。	类型为  R
     * @return
     * @title 分类排序器(中级方法)
     * @author: xue.zhang
     * @date 2019年2月2日上午9:45:33
     */
    public <R> List<TfwStatisticsModel<R, TechnicalFramework>> handler(Function<TechnicalFramework, R> classifier) {
        return this.handler(classifier, null, R -> R);// key值类型转换器，为原类型;
    }

    /**
     * @param classifier 分组器【必传】。为数据分组提供依据。	类型为  R
     * @param keyConverter 排序器【必传】，字段真实的类型 K
     * @param <K>
     * @return
     * @title 分类排序器(中级方法)
     * @author: xue.zhang
     * @date 2019年2月2日上午9:45:33
     */
    public <R, K> List<TfwStatisticsModel<K, TechnicalFramework>> handler(
            Function<TechnicalFramework, R> classifier,
            Function<R, K> keyConverter
    ) {
        return this.handler(classifier, null, keyConverter);// key值类型转换器，为原类型;
    }

    /**
     * @param classifier 分组器【必传】。为数据分组提供依据。	类型为  R
     * @param comparator 排序器【可选】。不传的时候，不排序  	类型为  K
     * @return
     * @title 分类排序器(中级方法)
     * @author: xue.zhang
     * @date 2019年2月2日上午9:45:33
     */
    public <R> List<TfwStatisticsModel<R, TechnicalFramework>> handler(
            Function<TechnicalFramework, R> classifier,
            Comparator<TfwStatisticsModel<R, TechnicalFramework>> comparator) {
        return this.handler(classifier, comparator, R -> R);// key值类型转换器，为原类型
    }

    /**
     * @param classifier 分组器【必传】。为数据分组提供依据。	类型为  R
     * @param comparator 排序器【可选】。不传的时候，不排序  	类型为  K
     * @param keyConverter 转换器【必传】，字段真实的类型 K
     * @param <K>
     * @return
     * @title 核心分类排序器(当前业务中的最底层方法)
     * @author: xue.zhang
     * @date 2019年2月2日上午9:45:33
     */
    public <R, K> List<TfwStatisticsModel<K, TechnicalFramework>> handler(
            Function<TechnicalFramework, R> classifier,
            Comparator<TfwStatisticsModel<K, TechnicalFramework>> comparator,
            Function<R, K> keyConverter
    ) {
        log.info("TechnicalFrameworkService#handler classifier={} comparator={} keyConverter={}", classifier,
                comparator, keyConverter);

        if (classifier == null || keyConverter == null) {
            return Lists.newArrayList();
        }

        // 查询全部数据
        List<TechnicalFramework> allTfw = this.technicalFrameworkDao
                .selectList(Wrappers.lambdaQuery());

        // 按照分组器进行分组
        Map<R, List<TechnicalFramework>> map = allTfw.stream().collect(Collectors.groupingBy(classifier));

        // 将分组后的数据封装为集合
        List<TfwStatisticsModel<K, TechnicalFramework>> result = map
                .keySet()
                .stream()
                .map(groupKey -> new TfwStatisticsModel<K, TechnicalFramework>(keyConverter.apply(groupKey),
                        map.get(groupKey)))
                .collect(Collectors.toList());

        if (comparator != null) {
            result.sort(comparator);
        }

        return result;
    }


}
